using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace DARE
{
    /// <summary>
    /// Represents a Bezier curve. Set every parameters points you want to create a bezier curve.
    /// You can also generate a defined number of points on this curve and get the one you want.
    /// </summary>
    /// <example>
    /// To get a point previously generated randomly
    /// <code>
    /// CSpline spline = new CSpline(4, new Vector3[4] { new Vector3(-200, 0, 0), new Vector3(-150, 0, 0), new Vector3(-100, 0, 0), new Vector3(-50, -80, 0) });
    /// Vector3 point = spline.Points[m_rand.Next(spline.NumPoints)];
    /// </code>
    /// </example>
    public class CSpline
    {
        Vector3[] m_parametersPoints;
        float[] px;
        float[] py;
        float[] pz;
        Vector3[] points;
        int numPoints;
        private float m_length;

        public Vector3[] Points { get { return points; } }

        public Vector3[] ParameterPoints { get { return m_parametersPoints; } }

        public float Length { get { return m_length; } }

        public int NumPoints
        {
            get { return numPoints; }
            set
            {
                if (numPoints != value)
                {
                    numPoints = value;
                    if (numPoints < m_parametersPoints.Length)
                        numPoints = m_parametersPoints.Length;
                    GenerateSpline();
                }
            }
        }

        public void RemoveParameterPoint()
        {
            if (m_parametersPoints.Length == 1)
                return;
            Vector3[] tmp = new Vector3[(int)m_parametersPoints.Length - 1];
            for (int i = 0; i < m_parametersPoints.Length - 1; ++i)
                tmp[i] = m_parametersPoints[i];
            m_parametersPoints = tmp;
            numPoints = m_parametersPoints.Length;
            initParametersPoint();
        }

        public void AddParameterPoint(Vector3 parameterPoint)
        {
            Vector3[] tmp = m_parametersPoints;
            m_parametersPoints = new Vector3[(int)m_parametersPoints.Length + 1];
            int i = 0;
            for (; i < tmp.Length; ++i)
                m_parametersPoints[i] = tmp[i];
            m_parametersPoints[i] = parameterPoint;
            numPoints = 50;
            initParametersPoint();
            m_length = 0;
            for (i = 1; i < points.Length; ++i)
                m_length += Vector3.Distance(points[i - 1], points[i]);

            numPoints = m_parametersPoints.Length;
            initParametersPoint();
        }

        private void initParametersPoint()
        {
            px = new float[m_parametersPoints.Length];
            py = new float[m_parametersPoints.Length];
            pz = new float[m_parametersPoints.Length];
            for (int i = 0; i < m_parametersPoints.Length; ++i)
            {
                px[i] = m_parametersPoints[i].X;
                py[i] = m_parametersPoints[i].Y;
                pz[i] = m_parametersPoints[i].Z;
            }
            GenerateSpline();
        }

        public CSpline(int numPoints, Vector3[] parameterPoints)
        {
            m_parametersPoints = new Vector3[parameterPoints.Length];
            for (int i = 0; i < parameterPoints.Length; ++i)
                m_parametersPoints[i] = parameterPoints[i];
            //this.numPoints = 50;
            this.numPoints = numPoints;
            initParametersPoint();
            m_length = 0;
            for (int i = 1; i < points.Length; ++i)
                m_length += Vector3.Distance(points[i - 1], points[i]);

            this.numPoints = numPoints;
            initParametersPoint();
        }

        //Useless for now but can be usefull later
        public void GenerateSpline()
        {
            int numParts = m_parametersPoints.Length - 1;
            points = new Vector3[numPoints];
            int i;
            int pointsCount = 0;
            float numPerThing = (numPoints / ((float)numParts)) * 3.0f;
            for (i = 0; i < numParts / 3; ++i)
            {
                if (i == (numParts / 3) - 1 && (numParts % 3 == 0))
                    numPerThing = numPoints - pointsCount;
                for (int t = 0; t < (int)numPerThing; ++t)
                {
                    float time = (float)(t) / (numPerThing - 1);
                    float x = makeBezier(time, px[0 + 3 * i], px[1 + 3 * i], px[2 + 3 * i], px[3 + 3 * i]);
                    float y = makeBezier(time, py[0 + 3 * i], py[1 + 3 * i], py[2 + 3 * i], py[3 + 3 * i]);
                    float z = makeBezier(time, pz[0 + 3 * i], pz[1 + 3 * i], pz[2 + 3 * i], pz[3 + 3 * i]);
                    points[pointsCount] = new Vector3(x, y, z);
                    ++pointsCount;
                }
            }
            if (numParts % 3 != 0)
            {
                int left = numParts % 3;
                int pointLeft = numPoints - pointsCount;
                switch (left)
                {
                    case 1:
                        for (int t = 0; t < pointLeft; ++t)
                        {
                            float time = (float)(t + 1) / (float)(pointLeft);
                            float x = makeBezier(time, px[0 + 3 * i], px[1 + 3 * i]);
                            float y = makeBezier(time, py[0 + 3 * i], py[1 + 3 * i]);
                            float z = makeBezier(time, pz[0 + 3 * i], pz[1 + 3 * i]);
                            points[pointsCount] = new Vector3(x, y, z);
                            ++pointsCount;
                        }
                        break;
                    case 2:
                        for (int t = 0; t < pointLeft; ++t)
                        {
                            float time = (float)(t + 1) / (float)(pointLeft);
                            float x = makeBezier(time, px[0 + 3 * i], px[1 + 3 * i], px[2 + 3 * i]);
                            float y = makeBezier(time, py[0 + 3 * i], py[1 + 3 * i], py[2 + 3 * i]);
                            float z = makeBezier(time, pz[0 + 3 * i], pz[1 + 3 * i], pz[2 + 3 * i]);
                            points[pointsCount] = new Vector3(x, y, z);
                            ++pointsCount;
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        public static float makeBezier(float t, float p0, float p1)
        {
            return ((1.0f - t) * p0) + (t * p1);
        }

        public static float makeBezier(float t, float p0, float p1, float p2)
        {
            return (float)Math.Pow(1.0f - t, 2.0f) * p0 + 2.0f * t * (1.0f - t) * p1 + t * t * p2;
        }

        public static float makeBezier(float t, float p0, float p1, float p2, float p3)
        {
            return (p0 * (float)Math.Pow(1.0f - t, 3) + (3.0f * p1 * t * (float)Math.Pow(1.0f - t, 2)) + (3.0f * p2 * t * t * (1.0f - t)) + (p3 * t * t * t));
        }

        public static Vector3 makeBezier(float t, Vector3 p0, Vector3 p1)
        {
            return ((1.0f - t) * p0) + (t * p1);
        }

        public static Vector3 makeBezier(float t, Vector3 p0, Vector3 p1, Vector3 p2)
        {
            return (float)Math.Pow(1.0f - t, 2.0f) * p0 + 2.0f * t * (1.0f - t) * p1 + t * t * p2;
        }

        public static Vector3 makeBezier(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
        {
            return (p0 * (float)Math.Pow(1.0f - t, 3) + (3.0f * p1 * t * (float)Math.Pow(1.0f - t, 2)) + (3.0f * p2 * t * t * (1.0f - t)) + (p3 * t * t * t));
        }

    }
}
